package com.teckteam.ptvsystem.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;

import com.teckteam.ptvsystem.model.facade.PTVConstants;

/**
 * Generalised class representing a collection of Stops. NOTE: ONE route is
 * defined as say, Werribee to Flinders St (City) as a single route. To define
 * City to Werribee, it must be set as a separate Route!
 * 
 */
public abstract class AbstractRoute
{
   private LinkedHashMap<String, AbstractStop> destinationList;
   private String transportType, routeName, timePeriod, stopID, direction, ampmCols[];
   private char dirFlag;
   
   public AbstractRoute(LinkedHashMap<String, String[]> inputList)
   {
      // TODO LATER - differentiate weekend timetables
      routeName = inputList.get("routeHeader")[0];
      stopID = inputList.get("routeHeader")[3];
   }
   
   /**
    * TODO used for appending timetable variations to existing route
    * 
    * @param timeTable
    */
   protected void appendTimetable(LinkedHashMap<String, String[]> timeTable)
   {
      //TODO - currently not implemented- to be used for optimization and better organising timetables of existing stops.
   }
   
   /**
    * Definition to mandate all children have their own routine for creating stops.
    * 
    * @param inputList
    */
   protected abstract void createStops(LinkedHashMap<String, String[]> inputList);
   
   protected String getDirection()
   {
      return direction;
   }
   
   public char getDirectionFlag()
   {
      return this.dirFlag;
   }
   
   public String getRouteName()
   {
      return routeName;
   }
   
   public LinkedHashMap<String, AbstractStop> getStopListDebug()
   {
      return this.destinationList;
   }
   
   // Return as arraylist
   public ArrayList<AbstractStop> getStopList()
   {
      ArrayList<AbstractStop> stops = new ArrayList<AbstractStop>();
      Iterator iterator = destinationList.keySet().iterator();
      while (iterator.hasNext())
         stops.add(destinationList.get(iterator.next()));
      
      return stops;
   }
   
   /**
    * Reconstructs a LinkedHashMap containing String arrays from currently loaded database.
    * Structure is meant to replicate those scraped from the PTV timetables
    * 
    * @return parsedSource
    */
   public LinkedHashMap<String, String[]> getStopListParsedSource()
   {
      //Rebuild AbstractStop object list to String[]
      LinkedHashMap<String, String[]> parsedSource = new LinkedHashMap<String, String[]>();
      Iterator iterator = destinationList.keySet().iterator();
      String[] routeHeader = new String[5];
      
      //Reconstruct header
      //0-Name, 1-timePeriod, 2-direction, 3-stopID, 4-transportType      
      routeHeader[0] = routeName;
      routeHeader[1] = timePeriod;
      routeHeader[2] = direction;
      routeHeader[3] = stopID;
      routeHeader[4] = transportType;
      parsedSource.put("routeHeader", routeHeader);
      
      //Reconstruct ampmCols
      parsedSource.put("ampmCols", ampmCols);
      
      while(iterator.hasNext())
      {
         String i = iterator.next().toString();
         
         if(i.compareTo("ampmCols")!=0 && i.compareTo("routeHeader")!=0)
         {
            AbstractStop stop = destinationList.get(i);
            ArrayList<DepartureTime> times = stop.getDepartureTimes();
            DepartureTime[] timesArr = new DepartureTime[times.size()];
            timesArr = times.toArray(timesArr);
            String[] meep = new String[timesArr.length];
            
            for(int j = 0; j < timesArr.length; j++)
               meep[j] = timesArr[j].toString();
            
            parsedSource.put(i, meep);
         }
      }
      
      return parsedSource;
   }
   
   /**
    * Returns entire list of suburbs based on stop information
    * 
    * @return
    */
   public ArrayList<String> getSuburbList()
   {
      Iterator<String> iterator = destinationList.keySet().iterator();
      ArrayList<String> suburbs = new ArrayList<String>();
      Set<String> dupeFilter = new HashSet<String>();
      
      while (iterator.hasNext())
      {
         String s = destinationList.get(iterator.next()).getSuburb();
         if (dupeFilter.add(s))
            suburbs.add(s);
      }
      
      return suburbs;
   }
   
   /**
    * Returns the route's associated timetable's day coverage indicator.
    * 
    * @return timePeriod 
    */
   protected String getTimetablePeriod()
   {
      return timePeriod;
   }
   
   /**
    * Returns route's transport type as a String
    * 
    * @return
    */
   public String getTransportType()
   {
      return this.transportType;
   }
   
   /**
    * Used for preserving the ampmCol table headers from scraped data
    * 
    * @param ampmCols
    */
   protected void saveAmPmCols(String[] ampmCols)
   {
      this.ampmCols = ampmCols;
   }
   
   /**
    * Sets the travel direction for this route
    * 
    * 
    * @param directionFlag
    */
   protected void setDirection(char directionFlag)
   {
      //TODO - Use separate timetable variables to define direction.
      
      // process R or H direction flags
      switch (directionFlag) {
         case PTVConstants.TIMETABLE_OUTBOUND:
            direction = "Outbound";
            break;
         case PTVConstants.TIMETABLE_INBOUND:
            direction = "Inbound";
            break;
      }
      
      this.dirFlag = directionFlag;
   }
   
   /**
    * Called when subclasses complete their createStops routine.
    * 
    * @param stopList
    */
   protected void setStops(LinkedHashMap<String, AbstractStop> stopList)
   {
      this.destinationList = stopList;
   }
   
   /**
    * Set the timetable's timetable day coverage.
    * 
    * @param period
    */
   protected void setTimetablePeriod(String period)
   {
      this.timePeriod = period;
   }

   /**
    * Set the transport type of this route.
    * 
    * @param type
    */
   protected void setTransportType(String type)
   {
      this.transportType = type;
   }
   
   /**
    * Returns a String representation of the current route object.
    * 
    */
   public String toString()
   {
      return String.format("%s route: %s (%s) - %s", transportType, routeName,
            getDirection(), getTimetablePeriod());
   }
   
   /**
    * Compares the order of the journey between two stops.
    * This assumes the stops 
    * 
    * @param string
    * @param string2
    */
   public int getTravelOrder(String startName, String endName)
   {
      int startIndex = 0, endIndex = 0, i = 0;
      boolean startFound = false, endFound = false;
      
      Iterator iterator = destinationList.keySet().iterator();
      while (iterator.hasNext())
      {
         AbstractStop s = destinationList.get(iterator.next());
         String str = s.toString();
         if (!startFound && str.compareTo(startName)==0)
         {
            startIndex = i;
            startFound = true;
         }
         if (!endFound && str.compareTo(endName)==0)
         {
            endIndex = i;
            endFound = true;
         }
         
         i++;
      }
      
      if(!startFound && !endFound) 
         return PTVConstants.FAILED_COMPARE;
      
      return endIndex - startIndex;
   }
   
   /**
    * By rotating the timetable, this obtains the departure times for one train.
    * 
    * @return
    */
   public ArrayList<DepartureTime> getVehicleTripFromTime(
         DepartureTime time, AbstractStop stop)
   {
      boolean found = true;
      Iterator iterator = destinationList.keySet().iterator();
      ArrayList<DepartureTime> vehicleTimes = new ArrayList<DepartureTime>();
      int i = 0,
          stopIndex = getStopIndex(stop); //Check if correct stop
      
      if (stopIndex == PTVConstants.FAILED_COMPARE)
         return vehicleTimes;
      
      for(DepartureTime d : stop.getDepartureTimes())
      {
         if(time.compareTo(d)==0)
         {
            found = true;
            break;
         }
         i++;
      }
      
      while (iterator.hasNext() && found)
      {
         String key = iterator.next().toString();
         AbstractStop s = destinationList.get(key);
         try
         {
            vehicleTimes.add(s.getDepartureTimes().get(i));
         } catch (IndexOutOfBoundsException e)
         {
            System.err.println(e.getMessage());
         }
      }
      
      if(vehicleTimes.size()==0) return null;
      
      return vehicleTimes;
   }
   
   /**
    * Returns the index of a stop in a route (from start to finish)
    * @param stop
    */
   public int getStopIndex(AbstractStop stop)
   {
      Iterator iterator = destinationList.keySet().iterator();
      int i = 0;
      
      while (iterator.hasNext())
      {
         AbstractStop s = destinationList.get(iterator.next());
         if (s.toString().compareTo(stop.toString())==0)
            return i;
      }
      
      return PTVConstants.FAILED_COMPARE;
   }
}
